#include <iostream>
#include <vector>
#include <set>
#include <mutex>
#include <thread>
#include <chrono>
#include <boost/asio.hpp>
#include <fstream>
#include <memory>

using namespace std;
using namespace boost::asio;

class session_base
{
public:
    ~session_base(){};
};

typedef std::shared_ptr<session_base> session_ptr;


class session
    : public session_base
{
public:
    static constexpr int buffered_size = 1;
    session(ip::tcp::socket socket, std::set<session_ptr>& sessions)
        : socket_(std::move(socket)),
          sessions_(sessions)
    {
        cout << "session with " 
                << socket_.remote_endpoint().address() 
                << " established" << std::endl;
    }
    void do_read()
    {
        char *buf_ = new char[buffered_size];
        async_read(socket_, buffer(buf_, buffered_size),
            [this, buf_](boost::system::error_code ec, std::size_t sz/*length*/){
                if(!ec){
                    cout << buf_[0];
                    delete[] buf_;
                    do_read();
                }
                else{
                    socket_.close();
                }
            });
    }
private:
    ip::tcp::socket socket_;
    std::set<session_ptr>& sessions_;
};

class csn_server
{
public:
    csn_server(io_context& context,
        ip::tcp::endpoint& endpoint)
        : acceptor_(context, endpoint)
    {
        cout << "server started" << endl;
        do_accept();
    }

private:
    ip::tcp::acceptor acceptor_;
    void do_accept()
    {
        acceptor_.async_accept(
            [this](boost::system::error_code ec, ip::tcp::socket socket){
                if (!ec){
                    cout << socket.remote_endpoint().address() << " connected" << endl;
                    auto ptr = std::make_shared<session>(std::move(socket), sessions_);
                    sessions_.insert(ptr);
                    ptr->do_read();
                }
                do_accept();
            });
    }
    std::set<session_ptr> sessions_; // use this to hold sessions_ptr
};

int main(int argc, char const *argv[])
{
    if (argc != 2){
        cout << "Usage: " << argv[0] 
        << "  <port>" << endl;
        return 1;
    }
    cout << "Port: " << argv[1] << endl;

    io_context context;
    ip::tcp::endpoint endpoint(ip::tcp::v4(), atoi(argv[1]));

    csn_server server(context, endpoint);
    context.run();
    return 0;
}
